// SKNotes - a note taking and gathering tool
// Copyright (C) 2009  Stefan Kueng
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;

namespace SKNotes.CustomControls
{

    /// <summary>
    /// Implements the INotifyPropertyChanged interface and 
    /// exposes a RaisePropertyChanged method for derived 
    /// classes to raise the PropertyChange event.  The event 
    /// arguments created by this class are cached to prevent 
    /// managed heap fragmentation.
    /// </summary>
    [Serializable]
    public abstract class BindableObject : INotifyPropertyChanged
    {
        #region Data

        private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache;
        private const string ERROR_MSG = "{0} is not a public property of {1}";

        #endregion // Data

        #region Constructors

        static BindableObject( )
        {
            eventArgCache = new Dictionary<string, PropertyChangedEventArgs>( );
        }

        protected BindableObject( )
        {
        }

        #endregion // Constructors

        #region Public Members

        /// <summary>
        /// Raised when a public property of this object is set.
        /// </summary>
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Returns an instance of PropertyChangedEventArgs for 
        /// the specified property name.
        /// </summary>
        /// <param name="propertyName">
        /// The name of the property to create event args for.
        /// </param>		
        public static PropertyChangedEventArgs
            GetPropertyChangedEventArgs(string propertyName)
        {
            if ( String.IsNullOrEmpty( propertyName ) )
                throw new ArgumentException(
                    "propertyName cannot be null or empty." );

            PropertyChangedEventArgs args;

            // Get the event args from the cache, creating them
            // and adding to the cache if necessary.
            lock ( typeof( BindableObject ) )
            {
                if ( !eventArgCache.TryGetValue( propertyName, out args ) ) // ContainsKey and indexer together
                {
                    eventArgCache.Add( propertyName, args = new PropertyChangedEventArgs( propertyName ) ); // No need to get newly created args from dictionary
                }
            }

            return args;
        }

        #endregion // Public Members

        #region Protected Members

        /// <summary>
        /// Derived classes can override this method to
        /// execute logic after a property is set. The 
        /// base implementation does nothing.
        /// </summary>
        /// <param name="propertyName">
        /// The property which was changed.
        /// </param>
        protected virtual void AfterPropertyChanged(string propertyName)
        {
        }

        /// <summary>
        /// Attempts to raise the PropertyChanged event, and 
        /// invokes the virtual AfterPropertyChanged method, 
        /// regardless of whether the event was raised or not.
        /// </summary>
        /// <param name="propertyName">
        /// The property which was changed.
        /// </param>
        protected void RaisePropertyChanged(string propertyName)
        {
            this.VerifyProperty( propertyName );

            PropertyChangedEventHandler handler = this.PropertyChanged;
            if ( handler != null )
            {
                // Get the cached event args.
                PropertyChangedEventArgs args = 
					GetPropertyChangedEventArgs( propertyName );

                // Raise the PropertyChanged event.
                handler( this, args );
            }

            this.AfterPropertyChanged( propertyName );
        }

        #endregion // Protected Members

        #region Private Helpers

        [Conditional( "DEBUG" )]
        private void VerifyProperty(string propertyName)
        {
            Type type = this.GetType( );

            // Look for a public property with the specified name.
            PropertyInfo propInfo = type.GetProperty( propertyName );

            if ( propInfo == null )
            {
                // The property could not be found,
                // so alert the developer of the problem.

                string msg = string.Format(
                    ERROR_MSG,
                    propertyName,
                    type.FullName );

                Debug.Fail( msg );
            }
        }

        #endregion // Private Helpers
    }

}